<!-- Licensed under a BSD license. See license.html for license -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<title>WebGL - Per Face Per Vertex vs Shared Normals Diagram</title>
<link type="text/css" href="../../resources/webgl-tutorials.css" rel="stylesheet" />
<style>
@media (prefers-color-scheme: dark) {
  canvas { 
    background: #444;
  }
}
</style>
</head>
<body>
<div class="description">
</div>
<div style="position:absolute;">
  <canvas id="canvas" width="400" height="300" style="width: 400px; height: 300px;"></canvas>
</div>
</body>
</html>
<!--
for most samples webgl-utils only provides shader compiling/linking and
canvas resizing because why clutter the examples with code that's the same in every sample.
See https://webglfundamentals.org/webgl/lessons/webgl-boilerplate.html
and https://webglfundamentals.org/webgl/lessons/webgl-resizing-the-canvas.html
for webgl-utils, m3, m4, and webgl-lessons-ui.
-->
<script src="../../resources/webgl-utils.js"></script>
<script src="../../resources/lessons-helper.js"></script> <!-- you can and should delete this script. it is only used on the site to help with errors -->
<script src="../../resources/twgl-full.min.js"></script>
<script id="vertexColorVertexShader" type="text/something-not-javascript">
attribute vec4 position;
attribute vec4 color;
uniform mat4 u_worldViewProjection;
varying vec4 v_color;
void main() {
  gl_Position = u_worldViewProjection * position;
  v_color = color;
}

</script>
<script id="vertexColorFragmentShader" type="text/something-not-javascript">
precision mediump float;
uniform vec4 u_color;
varying vec4 v_color;
void main() {
  gl_FragColor = u_color * v_color;
}
</script>
<script id="vertexColorFakeLightVertexShader" type="text/something-not-javascript">
attribute vec4 position;
attribute vec3 normal;
uniform mat4 u_world;
uniform mat4 u_worldViewProjection;
uniform mat4 u_worldInverseTranspose;
uniform mat4 u_viewInverse;
uniform vec3 u_lightPosition;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;
void main() {
  gl_Position = u_worldViewProjection * position;
  v_normal = (u_worldInverseTranspose * vec4(normal, 0)).xyz;
  v_surfaceToLight = u_lightPosition - (u_world * position).xyz;
  v_surfaceToView = (u_viewInverse[3] - (u_world * position)).xyz;
}

</script>
<script id="vertexColorFakeLightFragmentShader" type="text/something-not-javascript">
precision mediump float;
uniform vec4 u_color;
uniform float u_specular;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;

void main() {
  vec3 normal = normalize(v_normal);
  vec3 surfaceToLight = normalize(v_surfaceToLight);
  vec3 surfaceToView = normalize(v_surfaceToView);
  vec3 halfVector = normalize(surfaceToLight + surfaceToView);
  float lit = dot(normal, surfaceToLight) * .5 + .5;
  gl_FragColor = vec4(u_color.rgb * lit, u_color.a);
}
</script>
<script>
"use strict";
var m4 = twgl.m4;
var v3 = twgl.v3;

var fPosition;
var cameraPosition;
var eyePosition;
var target;
var gridScale = 400;
var et = 1 / 30;
var mode;

// globals
var canvas;               // the canvas
var devicePixelRatio = window.devicePixelRatio || 1;

function rand(min, max) {
  if (max === undefined) {
    max = min;
    min = 0;
  }
  return Math.random() * (max - min) + min;
}

function createGrid(size, subdivisions) {
  var numLines = subdivisions;
  var numVertices = numLines * 4;
  var positions = twgl.primitives.createAugmentedTypedArray(3, numVertices);
  var colors = twgl.primitives.createAugmentedTypedArray(4, numVertices);

  //  ..|..|..|..
  //  <-  size ->
  var black = [0, 0, 0, 1];
  var gray = [0.75, 0.75, 0.75, 1];

  var gridSize = size / (subdivisions + 2);
  for (var ii = 0; ii < numLines; ++ii) {
    var jj = ii - ((numLines - 1) / 2);
    var p = jj * gridSize;
    positions.push([p, 0, -size / 2]);
    positions.push([p, 0,  size / 2]);
    positions.push([-size / 2, 0, p]);
    positions.push([ size / 2, 0, p]);
    var color = jj ? gray : black;
    colors.push(color);
    colors.push(color);
    colors.push(color);
    colors.push(color);
  }

  return {
    position: positions,
    color: colors,
  };
}

function createNormalLines(vertices) {
  var scale = 10;
  var srcPositions = vertices.position;
  var srcNormals = vertices.normal;

  const normVertices = [];

  for (var ii = 0; ii < srcPositions.length; ii += 3) {
    const cube = twgl.primitives.createCubeVertices(1);
    delete cube.normal;
    delete cube.texcoord;
    cube.color = new Float32Array(cube.position.length / 3 * 4);
    cube.color.forEach((v, i) => {
      const ch = i % 4;
      cube.color[i] = ch === 3
          ? 1
          : srcNormals[ii + ch] * 0.5 + 0.5;
    });
    const target = srcNormals.slice(ii, ii + 3);
    const self = [0, 0, 0];
    const up = Math.abs(target[1]) === 1 ? [1, 0, 0] : [0, 1, 0];
    const lookAt = m4.lookAt(self, target, up);
    let mat = m4.identity();
    mat = m4.translate(mat, srcPositions.slice(ii, ii + 3));
    mat = m4.multiply(mat, lookAt);
    mat = m4.scale(mat, [1, 1, scale]);
    mat = m4.translate(mat, [0, 0, -0.5]);
    twgl.primitives.reorientVertices(cube, mat);
    normVertices.push(cube);
  }

  return twgl.primitives.concatVertices(normVertices);
}

function createApp(gl) {

  // Create Shader Programs
  var vertexColorProgramInfo = twgl.createProgramInfo(gl, [
      'vertexColorVertexShader',
      'vertexColorFragmentShader',
  ]);
  var litProgramInfo = twgl.createProgramInfo(gl, [
      'vertexColorFakeLightVertexShader',
      'vertexColorFakeLightFragmentShader',
  ]);

  var m = m4.identity();
  m4.rotateX(m, Math.PI * 0.5, m);
  m4.translate(m, [0.5, 0, -0.5], m);
  var sphereVertices = {
    position: [
      -35, -35, -35,  //     0------1
       35, -35, -35,  //    /|     /|
      -35,  35, -35,  //   / |    / |
       35,  35, -35,  //  2------3  |
      -35, -35,  35,  //  |  4---|--5
       35, -35,  35,  //  | /    | /
      -35,  35,  35,  //  |/     |/
       35,  35,  35,  //  6------7
    ],
    normal: [
      -0.333, -0.333, -0.333,
       0.333, -0.333, -0.333,
      -0.333,  0.333, -0.333,
       0.333,  0.333, -0.333,
      -0.333, -0.333,  0.333,
       0.333, -0.333,  0.333,
      -0.333,  0.333,  0.333,
       0.333,  0.333,  0.333,
    ],
    indices: [
       0, 1, 2, 1, 2, 3,
       4, 5, 6, 5, 6, 7,
       0, 2, 4, 4, 2, 6,
       3, 1, 7, 7, 1, 5,
       1, 0, 4, 4, 0, 5,
       2, 3, 6, 6, 3, 7,
    ],
  };
  var sphereBufferInfo = twgl.createBufferInfoFromArrays(gl, sphereVertices);
  var sphereNormalsBufferInfo = twgl.createBufferInfoFromArrays(gl, createNormalLines(sphereVertices));
  var cubeVertices = twgl.primitives.createCubeVertices(70);
  var cubeBufferInfo = twgl.createBufferInfoFromArrays(gl, cubeVertices);
  var cubeNormalsBufferInfo = twgl.createBufferInfoFromArrays(gl, createNormalLines(cubeVertices));
  // Create Geometry.
  var gridArrays = createGrid(26, 42);
  var gridBufferInfo = twgl.createBufferInfoFromArrays(gl, gridArrays);

  // pre-allocate a bunch of arrays
  var projection = m4.identity();
  var inverseProjection = m4.identity();
  var exampleProjection = m4.identity();
  var exampleInverseProjection = m4.identity();
  var view = m4.identity();
  var viewInverse = m4.identity();
  var world = m4.identity();
  var worldInverseTranspose = m4.identity();
  var scaleMat = m4.identity();
  var viewProjection = m4.identity();
  var inverseViewProjection = m4.identity();
  var worldViewProjection = m4.identity();
  var exampleWorldViewProjection = m4.identity();
  fPosition = new Float32Array([-50, 50, -150]);
  eyePosition = new Float32Array([150, 150, 150]);
  target = new Float32Array([0, 0, 0]);
  cameraPosition = new Float32Array([50, 200, 100]);
  var up = new Float32Array([0,1,0]);
  var v3t0 = new Float32Array(3);
  var v3t1 = new Float32Array(3);
  var v3t2 = new Float32Array(3);
  var v3t3 = new Float32Array(3);
  var m4t0 = m4.identity();
  var m4t1 = m4.identity();
  var m4t2 = m4.identity();
  var m4t3 = m4.identity();
  var zeroMat = m4.identity();
  var zero4 = new Float32Array(4);
  var one4 = new Float32Array([1,1,1,1]);
  var black = new Float32Array([0,0,0,1]);
  var red = new Float32Array([1,0,0,1]);
  var green = new Float32Array([0,1,0,1]);
  var blue = new Float32Array([0,0,1,1]);
  var ltRed = new Float32Array([1,0.5,0.5,1]);
  var ltGreen = new Float32Array([0.5,1,0.5,1]);
  var ltBlue = new Float32Array([0.5,0.5,1,1]);
  var white = new Float32Array([1,1,1,1]);
  var flashColor = new Float32Array([1,1,1,0.75]);
  var selectPoint = [0,0,0];

  // uniforms.
  var sharedUniforms = {
    u_world: world,
    u_worldViewProjection: worldViewProjection,
    u_worldInverseTranspose: worldInverseTranspose,
    u_viewInverse: viewInverse,
    u_lightPosition: [200, 500, -300],
    u_specular: 1000,
  };

  var gridUniforms = {
    u_color: [0.3,0.3,0.3,1],
  };

  var normalUniforms = {
    u_color: [1, 1, 1, 1],
  };

  var sphereUniforms = {
    u_color: [1, 0, 0, 1],
  };

  var cubeUniforms = {
    u_color: [0, 0, 1, 1],
  };

  function radToDeg(r) {
    return r * 180 / Math.PI;
  }

  function drawModel(programInfo, bufferInfo, type, uniforms, world) {
    m4.multiply(viewProjection, world, worldViewProjection);
    m4.inverse(world, worldInverseTranspose);
    m4.transpose(worldInverseTranspose, worldInverseTranspose);
    gl.useProgram(programInfo.program);
    twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
    twgl.setUniforms(programInfo, sharedUniforms, uniforms);
    twgl.drawBufferInfo(gl, bufferInfo, type);
  }

  function degToRad(deg) {
    return deg * Math.PI / 180;
  }

  var clock = 0;
  var frameCount = 0;
  function render(elapsedTime) {
    clock += elapsedTime;
    ++frameCount;

    // clear the screen.
    gl.enable(gl.DEPTH_TEST);
//    gl.colorMask(true, true, true, true);
//    gl.clearColor(1, 1, 1, 1);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

    var aspect = canvas.clientWidth / canvas.clientHeight;
    m4.perspective(
        degToRad(40),
        aspect,
        1,
        5000,
        projection);

    var dist = v3.length(v3.subtract(target, eyePosition));
    var a = Math.PI * -.5;//clock / 8;
    v3t0[0] = Math.cos(a) * dist + target[0];
    v3t0[1] = eyePosition[1];
    v3t0[2] = Math.sin(a) * dist + target[2];
    m4.lookAt(
        v3t0,
        target,
        up,
        viewInverse);
    m4.inverse(viewInverse, view);
    m4.multiply(projection, view, viewProjection);
    m4.inverse(viewProjection, inverseViewProjection);

    m4.translation([-60, 0, 0], world);
    m4.rotateY(world, clock / 8, world);
    drawModel(litProgramInfo, sphereBufferInfo, gl.TRIANGLES, sphereUniforms, world);
    drawModel(vertexColorProgramInfo, sphereNormalsBufferInfo, gl.TRIANGLES, normalUniforms, world);
    m4.translation([ 60, 0, 0], world);
    m4.rotateY(world, clock / 8, world);
    drawModel(litProgramInfo, cubeBufferInfo, gl.TRIANGLES, cubeUniforms, world);
    drawModel(vertexColorProgramInfo, cubeNormalsBufferInfo, gl.TRIANGLES, normalUniforms, world);
    //m4.translation([0, -100, 0], world);
    //m4.scale(world, [100, 100, 100], world);
    //drawModel(vertexColorProgramInfo, gridBufferInfo, gl.LINES, gridUniforms, world);
  }

  return {
    render: render
  };
}

var app;
function main() {
  canvas = document.querySelector("#canvas");

  var gl = twgl.getWebGLContext(canvas, {alpha: true, preMultipliedAlpha: false});
  if (!gl) {
    return;
  }

  twgl.resizeCanvasToDisplaySize(gl.canvas, window.devicePixelRatio);
  gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

  app = createApp(gl);

  var then = 0;
  function render(time) {
    var now = time * 0.001;
    var elapsedTime = now - then;
    then = now;
    app.render(elapsedTime);
    requestAnimationFrame(render);
  }
  requestAnimationFrame(render);
}

main();
</script>




